package com.bitcointoolkit.web.service.tx;

import com.alibaba.fastjson.annotation.JSONField;
import com.bitcointoolkit.common.cash.EncodeUtil;

import java.util.Arrays;


/**
 * [类描述]
 *
 * @author caican
 * @date 18/6/17
 */
public abstract class TxScript {

    private final int MAX_SCRIPT_SIZE = 10000;

    @JSONField(serialize=false)
    public boolean isPayToScriptHash(){
        return hex.length == 23 && hex[0] == OpCodeType.OP_HASH160.getCode()
                && hex[1] == 0x14 && hex[22] == OpCodeType.OP_EQUAL.getCode();
    }

    TxType txType;


    @JSONField(serialize=false)
    int[] hex;


    // 以下为JSON序列化数据
    private String scriptHex;
    private String scriptAsm;

    public TxScript(String scriptHex) {
        this.scriptHex = scriptHex;
        hex = EncodeUtil.toInts(scriptHex);
        toAsm();
        judgeType();
    }

    /**
     * 判断 交易类型，输出脚本才需要
     * @return 交易类型
     */
    protected abstract TxType judgeType();

    private void toAsm() {
        this.scriptAsm = toAsm(false);
    }

    public String toAsm(boolean decode) {
        int startPos = 0;
        StringBuffer sb = new StringBuffer();
        while (startPos < hex.length) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            OpResult opResult = getOp(startPos, true);
            if (!opResult.isSuccess()) {
                sb.append("[error]");
                return sb.toString();
            }
            startPos = opResult.getCurrentPos();
            if (opResult.getOpCode() >=0 && opResult.getOpCode() <= OpCodeType.OP_PUSHDATA4.getCode()) {
                if (decode && isUnspendable()) {

                } else {
                    sb.append(EncodeUtil.parseToString(opResult.getData()));
                }
            } else {
                sb.append(OpCodeType.getOpCodeTypeByCode(opResult.getOpCode()).toString());
            }
        }
        return sb.toString();
    }

    public String getScriptHex() {
        return scriptHex;
    }

    public void setScriptHex(String scriptHex) {
        this.scriptHex = scriptHex;
    }

    public String getScriptAsm() {
        return scriptAsm;
    }

    public void setScriptAsm(String scriptAsm) {
        this.scriptAsm = scriptAsm;
    }

    public OpResult getOp(int currentPos) {
        return getOp(currentPos, false);
    }

    public OpResult getOp(int currentPos, boolean getData) {
        OpResult result = new OpResult().opCode(OpCodeType.OP_INVALIDOPCODE.getCode());
        if (currentPos >= hex.length) {
            return result.success(false);
        }
        int opCode = hex[currentPos];
        currentPos++;
        if (opCode <= OpCodeType.OP_PUSHDATA4.getCode()) {
            int datalen = 0;
            if (opCode < OpCodeType.OP_PUSHDATA1.getCode()) {
                datalen = opCode;
            } else if (opCode < OpCodeType.OP_PUSHDATA1.getCode()) {
                if (currentPos >= hex.length) {
                    return result.success(false);
                }
                datalen = hex[currentPos];
                currentPos++;
            } else if (opCode < OpCodeType.OP_PUSHDATA2.getCode()) {
                if (currentPos > hex.length - 2) {
                    return result.success(false);
                }
                datalen = EncodeUtil.parseInt16(Arrays.copyOfRange(hex, currentPos, currentPos+2));
                currentPos += 2;
            } else if (opCode < OpCodeType.OP_PUSHDATA4.getCode()) {
                if (currentPos > hex.length - 4) {
                    return result.success(false);
                }
                datalen = (int)EncodeUtil.parseInt32(Arrays.copyOfRange(hex, currentPos, currentPos+4));
                currentPos += 4;
            }
            if (currentPos > hex.length || (hex.length - currentPos) < datalen) {
                return result.success(false);
            }
            if (getData) {
                result.data(Arrays.copyOfRange(hex, currentPos, currentPos+datalen));
            }
            currentPos += datalen;
        }

        return result.currentPos(currentPos).opCode(opCode).success(true);
    }

    public boolean isPushOnly(int startPos) {
        while (startPos < hex.length) {
            OpResult opResult = getOp(startPos);
            if (!opResult.isSuccess()) {
                return false;
            }
            if (opResult.getOpCode() > OpCodeType.OP_16.getCode()) {
                return false;
            }
            startPos = opResult.getCurrentPos();
        }
        return true;
    }

    @JSONField(serialize=false)
    public boolean isUnspendable() {
        return (hex.length > 0 && hex[0] == OpCodeType.OP_RETURN.getCode()) ||
                (hex.length > MAX_SCRIPT_SIZE);
    }
    @JSONField(serialize=false)
    public int getLenOfLen() {
        if (hex.length < 0xfd) {
            return 1;
        } else  {
            return 3;
        }
    }

    public final class OpResult {
        private boolean success;
        private int opCode;
        private int[] data;
        private int currentPos;

        public int getCurrentPos() {
            return currentPos;
        }

        public OpResult currentPos(int currentPos) {
            this.currentPos = currentPos;
            return this;
        }

        public boolean isSuccess() {
            return success;
        }

        public OpResult success(boolean success) {
            this.success = success;
            return this;
        }

        public int getOpCode() {
            return opCode;
        }

        public OpResult opCode(int opCode) {
            this.opCode = opCode;
            return this;
        }

        public int[] getData() {
            return data;
        }

        public OpResult data(int[] data) {
            this.data = data;
            return this;
        }
    }
}
